package com.planw.beetl.sql;

import com.intellij.codeInsight.completion.CompletionContributor;
import com.intellij.codeInsight.completion.CompletionParameters;
import com.intellij.codeInsight.completion.CompletionProvider;
import com.intellij.codeInsight.completion.CompletionResultSet;
import com.intellij.codeInsight.lookup.AutoCompletionPolicy;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.LookupElementBuilder;
import com.intellij.lang.ASTNode;
import com.intellij.lang.Language;
import com.intellij.openapi.project.Project;
import com.intellij.patterns.PatternCondition;
import com.intellij.patterns.PlatformPatterns;
import com.intellij.patterns.PsiElementPattern.Capture;
import com.intellij.psi.PsiAnnotation;
import com.intellij.psi.PsiAnnotationMemberValue;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiField;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiJvmMember;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.PsiNamedElement;
import com.intellij.psi.PsiParameter;
import com.intellij.psi.PsiType;
import com.intellij.psi.PsiTypeElement;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.util.PsiUtilCore;
import com.intellij.ui.JBColor;
import com.intellij.util.PlatformIcons;
import com.intellij.util.ProcessingContext;
import com.planw.beetl.model.TreeNode;
import com.planw.beetl.service.BeetlSqlService;
import com.planw.beetl.utils.PsiKt;
import com.planw.beetl.utils.StrUtil;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.Queue;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.intellij.plugins.markdown.lang.MarkdownElementTypes;
import org.intellij.plugins.markdown.lang.MarkdownLanguage;
import org.intellij.plugins.markdown.lang.MarkdownTokenTypeSets;
import org.intellij.plugins.markdown.lang.MarkdownTokenTypes;
import org.jetbrains.annotations.NotNull;


public class BeetlSqlCompletionContributor extends CompletionContributor {

  public BeetlSqlCompletionContributor() {

    Capture<PsiElement> pattern = PlatformPatterns.psiElement()
        .with(new MarkdownBeetlSqlPatternCondition());
    extend(null, pattern,
        new BeetlSqlCompletionProvider()
    );
  }

  static class MarkdownBeetlSqlPatternCondition extends PatternCondition<PsiElement> {

    public MarkdownBeetlSqlPatternCondition() {

      super("BeetlSqlPatternCondition");
    }

    @Override
    public boolean accepts(@NotNull PsiElement psiElement, ProcessingContext processingContext) {

      Project project = psiElement.getProject();
      BeetlSqlService beetlSqlService = project.getService(BeetlSqlService.class);
//      无beetlsql依赖，就跳过代码完成
      if (!beetlSqlService.isDependOnBeetlSql()) {
        return false;
      }
      /*
       * 关于idea的classloader的几点：
       * 1. 当前线程的classloader加载路径是运行时的idea下的lib目录
       * 2. GroupTemplate.class.getClassLoader() 这种方式获取的classloader是idea为每一个plugin独立的classloader。也只有这种方式，才能加载到我们引入的第三方jar
       * 3. ClassLoader.getSystemClassLoader() 获取的classloader也是运行时idea环境的lib目录
       * 4. 插件的class由pluginclassloader加载，必须在plugin.xml中配置depends，依赖插件的id
       * */
      Language language = PsiUtilCore.findLanguageFromElement(psiElement);
      if (!language.is(MarkdownLanguage.INSTANCE)) {
        return false;
      }
      return true;
//      PsiElement parent = psiElement.getParent();
////      这几个markdown元素是可能存在beetlsql语法的位置
//      if (parent instanceof MarkdownParagraphImpl || parent instanceof MarkdownCodeFenceImpl
//          || parent instanceof MarkdownCodeBlockImpl) {
//        return true;
//      }
//      return false;
    }

  }

  static class BeetlSqlCompletionProvider extends CompletionProvider<CompletionParameters> {

    Object[] args;

    // 用于搜索代码完成的文本 例如： sex user.name 这一行，我们只需要最后的user.name
    private static Pattern autocompletion = Pattern.compile("[\\w\\d\\.]+");

    @Override
    protected void addCompletions(@NotNull CompletionParameters parameters,
        @NotNull ProcessingContext context, @NotNull CompletionResultSet result) {

      PsiFile currentPsiFile = parameters.getOriginalFile();
      Project project = currentPsiFile.getProject();
      PsiElement backwordSetext = findMarkdownSETEXT(parameters);
      ASTNode sqlIdNode = backwordSetext.getNode()
          .findChildByType(MarkdownTokenTypes.SETEXT_CONTENT)
          .findChildByType(MarkdownTokenTypes.TEXT);
//      sqlid 既是mapper的方法名
      String sqlId = sqlIdNode.getText();
      BeetlSqlService beetlSqlService = BeetlSqlService.getInstance(project);
//      获取对应的mapper类
      PsiClass mapper = beetlSqlService.getMapperByMdFile(currentPsiFile);
      if (mapper != null) {
//      不查找父类方法
        PsiMethod[] methods = mapper.findMethodsByName(sqlId, true);
        PsiElement cursorElement = parameters.getOriginalPosition();
        String word = Optional.ofNullable(cursorElement).map(PsiElement::getText)
            .orElse(StringUtils.EMPTY);
        Matcher matcher = autocompletion.matcher(word);
        String t = StringUtils.EMPTY;
        while (matcher.find()) {
//          查找sex  user.name 文本的user.name 部分
          t = matcher.group();
        }
        word = t;
//        如果是点号结尾，给一个星号，说明查询点号后面的所有可能代码
        if (StringUtils.endsWith(word, ".")) {
          word = word + "*";
        }
        String[] searchStrs = word.split("\\.");
        if (ArrayUtils.isNotEmpty(searchStrs)) {
          addResultSet(result, methods, searchStrs);
          List<LookupElement> beetlSqlFunctions = beetlSqlService
              .findBeetlSqlFunction(searchStrs[0]);
          for (LookupElement element : beetlSqlFunctions) {
            result.addElement(element);
          }
        }
      }
    }

    private PsiElement findMarkdownSETEXT(@NotNull CompletionParameters parameters) {

      // MarkdownTokenTypes.SETEXT_1 的上一级就是psifile ，所以应该找到当前光标所处psifile下的第一级
      PsiElement curNode = parameters.getPosition();
      while (ObjectUtils
          .notEqual(Optional.ofNullable(curNode).map(PsiElement::getParent).map(PsiElement::getNode)
              .map(ASTNode::getElementType).orElse(null), MarkdownElementTypes.MARKDOWN_FILE)) {
        curNode = curNode.getParent();
      }
//      查找最近的Sql Id。既：markdown 语法中的 段落标题 === 语法
      return PsiTreeUtil
          .findSiblingBackward(curNode, MarkdownTokenTypeSets.SETEXT_1, null);
    }

    private void addResultSet(CompletionResultSet result, PsiMethod[] methods,
        String[] searchStrs) {

      for (PsiMethod method : methods) {
        PsiParameter[] psiParameters = method.getParameterList().getParameters();
        for (PsiParameter parameter : psiParameters) {
//          处理基本类型
          if (PsiKt.isPrimitive(parameter.getType())) {
            handlePrimitive(result, parameter, Arrays.asList(searchStrs), 0);
            continue;
          }
//          处理非基本类型参数
          dsfParameter(result, parameter, Arrays.asList(searchStrs));
        }
      }
    }

    private void handlePrimitive(CompletionResultSet result, PsiParameter parameter,
        List<String> searchStrs, int level) {

      String path = searchStrs.get(level);
//      优先 org.beetl.sql.core.annotatoin.Param 注解值作为代码提示值，注解只有一级，因为只对基本类型和string注解
      PsiAnnotation paramAnnotatioin = parameter.getAnnotation(BeetlSqlService.PARAM_CLASS);
      if (paramAnnotatioin != null) {
//        被注解的参数不是所需的时候直接跳过
        PsiAnnotationMemberValue value = paramAnnotatioin.findDeclaredAttributeValue("value");
        String text = Optional.ofNullable(value).map(PsiElement::getText).orElse("")
            .replaceAll("\"", "");
        if (StrUtil.searchCharSequence(text, path)) {
          LookupElement lookupElement = LookupElementBuilder
              .create(text)
              .withPresentableText(text)
              .withIcon(PlatformIcons.VARIABLE_ICON)
              .withItemTextForeground(JBColor.BLACK)
              .bold()
              .withTypeText("@Param", PlatformIcons.VARIABLE_ICON, true)
              .withTypeIconRightAligned(true)
              .withAutoCompletionPolicy(AutoCompletionPolicy.ALWAYS_AUTOCOMPLETE);
          result.addElement(lookupElement);
        }
      } else {
        String text = parameter.getName();
        if (StrUtil.searchCharSequence(text, path)) {
          LookupElement lookupElement = LookupElementBuilder
              .create(text)
              .withPresentableText(text)
              .withIcon(PlatformIcons.VARIABLE_ICON)
              .withItemTextForeground(JBColor.BLACK)
              .bold()
              .withTypeText(PsiKt.getClassName(parameter.getType()), PlatformIcons.CLASS_ICON,
                  true)
              .withTypeIconRightAligned(true)
              .withAutoCompletionPolicy(AutoCompletionPolicy.ALWAYS_AUTOCOMPLETE);

          result.addElement(lookupElement);
        }
      }
    }

    /**
     * 深度搜索字段，截止到；并且将搜索到的按照点号拼接
     */
    private void dsfParameter(CompletionResultSet result, PsiParameter parameter,
        List<String> searchStrs) {

//        判断PageQuery，并判断是否有泛型，没有直接跳过
      PsiClass entityPsiClass = null;
      if (StringUtils.equals(BeetlSqlService.PAGE_QUERY_CLASS,
          PsiUtil.resolveClassInType(parameter.getType()).getQualifiedName())) {
//        获取泛型参数
        PsiTypeElement[] typeParameterElements = parameter.getTypeElement()
            .getInnermostComponentReferenceElement().getParameterList().getTypeParameterElements();
        PsiType entityType = Arrays.stream(typeParameterElements).findFirst()
            .map(PsiTypeElement::getType)
            .orElse(null);
//        解析成psiclass
        entityPsiClass = PsiUtil.resolveClassInType(entityType);
      } else {
        entityPsiClass = PsiUtil.resolveClassInType(parameter.getType());
      }
      PsiField[] fields = entityPsiClass.getFields();
      Queue<String> searchStrQueue = new ArrayBlockingQueue<>(searchStrs.size(), false,
          searchStrs);
      TreeNode<PsiField> treeNode = new TreeNode<>();
      resolvePsiClass(treeNode, Arrays.asList(fields),
          searchStrQueue);
      handleTreeNodeToLookupElement(result, treeNode);
    }

    /**
     * 深度遍历实体类字段。比如user.name  user搜索当前实体类字段，name搜索字段可能的实体类的字段，也就是字段的字段
     *
     * @param topNode
     *     用于保存查询到的可能psifield
     * @param searchQueue
     *     以点号分割的用户输入的文本，例如 user.name 变成 user name 两个。
     */
    private void resolvePsiClass(TreeNode<PsiField> topNode,
        List<PsiField> fields, Queue<String> searchQueue) {

      String searchStr = searchQueue.poll();
      if (StringUtils.isBlank(searchStr)) {
        return;
      }
      for (PsiField field : fields) {
        if (StrUtil.searchCharSequence(field.getName(), searchStr)) {
          TreeNode<PsiField> node = new TreeNode<>(field);
          topNode.addChild(node);
          PsiType fieldType = field.getType();
          if (!PsiKt.isPrimitive(fieldType)) {
            PsiClass fieldPsiCls = PsiUtil.resolveClassInType(fieldType);
            PsiField[] fieldPsiClsFields = Optional.ofNullable(fieldPsiCls).map(PsiClass::getFields)
                .orElse(new PsiField[0]);
            resolvePsiClass(node, Arrays.asList(fieldPsiClsFields), searchQueue);
          }
        }
      }
    }

    /**
     * 深度遍历treenode，每个叶节点的路径为一个lookupelement
     */
    private void handleTreeNodeToLookupElement(CompletionResultSet result,
        TreeNode<PsiField> treeNode) {

      dsfTreeNode(result, treeNode.getChildren(), "");
    }

    private void dsfTreeNode(CompletionResultSet result, List<TreeNode<PsiField>> children,
        String lookupStr) {

      for (TreeNode<PsiField> child : children) {
        PsiField psiField = child.getValue();
        String tempLookupStr = lookupStr + psiField.getName() + ".";
        List<TreeNode<PsiField>> childChildren = child.getChildren();
        if (!childChildren.isEmpty()) {
          dsfTreeNode(result, childChildren, tempLookupStr);
        } else {
          String text = tempLookupStr.substring(0, tempLookupStr.length() - 1);
          LookupElement lookupElement = LookupElementBuilder
              .createWithSmartPointer(text, psiField.getParent())
              .withPresentableText(text)
              .withIcon(PlatformIcons.VARIABLE_ICON)
              .withItemTextForeground(JBColor.BLACK)
              .bold()
              .withTypeText(Optional.ofNullable(psiField).map(PsiJvmMember::getContainingClass).map(
                  PsiNamedElement::getName).orElse(StringUtils.EMPTY), PlatformIcons.CLASS_ICON,
                  true)
              .withTypeIconRightAligned(true)
              .withAutoCompletionPolicy(AutoCompletionPolicy.ALWAYS_AUTOCOMPLETE);

          result.addElement(lookupElement);
        }
      }
    }

  }

}